// Copyright (C) 1999-2000 Id Software, Inc.
//
// g_weapon.c 
// perform the server side effects of a weapon firing

#include "g_local.h"

// BEGIN bots - spin
#include "bots_local.h"
#include "bots_soldier.h"
#include "bots_berzerker.h"
#include "bots_kamikazee.h"
#include "bots_sniper.h"
#include "bots_nurse.h"
#include "bots_Scientist.h"
// END bots - spin   

static	float	s_quadFactor;
static	vec3_t	forward, right, up;
static	vec3_t	muzzle;


// BEGIN bots - tbp
static int BOTS_modifyDamage(gentity_t *ent, int iNominalDamage) {
	return (int) ((float)iNominalDamage * s_quadFactor) * (1.0f + (ent->fCachedWeaponDamage/4.0f));	
}

static void BOTS_computeQuadFactor(gentity_t *ent) {
	s_quadFactor = 1;
	if (ent->client) {
		if (ent->client->ps.powerups[PW_QUAD])
			s_quadFactor = g_quadfactor.value;
		
		// Warcry damage bonus
		if (ent->client->ps.powerups[PW_WARCRY])
			s_quadFactor *= 1.50f; // +50%
	}
}

// END bots - tbp
/*
======================================================================

GAUNTLET

======================================================================
*/

void Weapon_Gauntlet( gentity_t *ent ) {

}

/*
===============
CheckGauntletAttack
===============
*/
qboolean CheckGauntletAttack( gentity_t *ent ) {
	/*
	trace_t		tr;
	vec3_t		end;
	gentity_t	*tent;
	gentity_t	*traceEnt;
	*/
	int			damage;

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, forward, right, up);

	CalcMuzzlePoint ( ent, forward, right, up, muzzle );

	BOTS_computeQuadFactor(ent);
	damage = (BOTS_GAUNTLET_DAMAGE + (BOTS_GAUNTLET_DAMAGE * .25 * ent->client->pers.classlevel));
	damage = BOTS_modifyDamage(ent,damage);
// BEGIN bots - nits
	// Calculate the other damage vectors for the berzerker gauntlet
	// lets just have this function do them all.
	// if (ent->bots_class == BOTS_BERZERKER)
	return BOTS_calc_other_gaunt_vectors(ent, damage, forward, right, up, muzzle);
// END bots - nits

/*
	VectorMA (muzzle, 32, forward, end);

	trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
	if ( tr.surfaceFlags & SURF_NOIMPACT ) {
		return qfalse;
	}

	traceEnt = &g_entities[ tr.entityNum ];

	// send blood impact
	if ( traceEnt->takedamage && traceEnt->client ) {
		tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
		tent->s.otherEntityNum = traceEnt->s.number;
		tent->s.eventParm = DirToByte( tr.plane.normal );
		tent->s.weapon = ent->s.weapon;
	}

	if ( !traceEnt->takedamage) {
		return qfalse;
	}

	if (ent->client->ps.powerups[PW_QUAD] ) {
		G_AddEvent( ent, EV_POWERUP_QUAD, 0 );
		s_quadFactor = g_quadfactor.value;
	} else {
		s_quadFactor = 1;
	}

// BEGIN bots - spin
//	damage = 50 * s_quadFactor;
	damage = BOTS_GAUNTLET_DAMAGE * s_quadFactor;
	damage = damage + damage * (bots_getTechLevel(ent, SCI_WEAPON_DAMAGE) / 2);
// END bots - spin

	G_Damage( traceEnt, ent, ent, forward, tr.endpos,
		damage, 0, MOD_GAUNTLET );

	return qtrue;
*/

}


/*
======================================================================

MACHINEGUN

======================================================================
*/

/*
======================
SnapVectorTowards

Round a vector to integers for more efficient network
transmission, but make sure that it rounds towards a given point
rather than blindly truncating.  This prevents it from truncating 
into a wall.
======================
*/
void SnapVectorTowards( vec3_t v, vec3_t to ) {
	int		i;

	for ( i = 0 ; i < 3 ; i++ ) {
		if ( to[i] <= v[i] ) {
			v[i] = (int)v[i];
		} else {
			v[i] = (int)v[i] + 1;
		}
	}
}

#define MACHINEGUN_SPREAD	200
#define	MACHINEGUN_DAMAGE	7
#define	MACHINEGUN_TEAM_DAMAGE	5		// wimpier MG in teamplay

void Bullet_Fire (gentity_t *ent, float spread, int damage ) {
	trace_t		tr;
	vec3_t		end;
	float		r;
	float		u;
	gentity_t		*tent;
	gentity_t		*traceEnt;

	damage *= s_quadFactor;

	r = crandom()*spread;
	u = crandom()*spread;
	VectorMA (muzzle, 8192, forward, end);
	VectorMA (end, r, right, end);
	VectorMA (end, u, up, end);

	trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
	if ( tr.surfaceFlags & SURF_NOIMPACT ) {
		return;
	}

	traceEnt = &g_entities[ tr.entityNum ];

	// snap the endpos to integers, but nudged towards the line
	SnapVectorTowards( tr.endpos, muzzle );

	// send bullet impact
	if ( traceEnt->takedamage && traceEnt->client ) {
		tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
		tent->s.eventParm = traceEnt->s.number;
		if( LogAccuracyHit( traceEnt, ent ) ) {
			ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
		}
	} else {
		tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
		tent->s.eventParm = DirToByte( tr.plane.normal );
	}
	tent->s.otherEntityNum = ent->s.number;

	if ( traceEnt->takedamage) {
		G_Damage( traceEnt, ent, ent, forward, tr.endpos,
			damage, 0, MOD_MACHINEGUN);
	}
}


/*
======================================================================

BFG

======================================================================
*/

void BFG_Fire ( gentity_t *ent ) {
	gentity_t	*bfg1;
	gentity_t	*bfg2;
	gentity_t	*bfg3;

	bfg1 = fire_bfg (ent, muzzle, forward);
	bfg1->damage		= BOTS_modifyDamage(ent,bfg1->damage);
	bfg1->splashDamage  = BOTS_modifyDamage(ent,bfg1->splashDamage);

	if (ent->bots_class == BOTS_CAPTAIN && ent->client->weapontoggle)
	{
		vec3_t	forward2, forward3;
		int		horizontal;
		int		vertical;

		if (ent->client->i_splitmode == 1)
		{
			horizontal	= 40;
			vertical	= -40;
		}
		else if (ent->client->i_splitmode == 2)
		{
			horizontal	= 70;
			vertical	= -20;
		}
		else
		{
			horizontal	= 95;
			vertical	= -20;
		}

		VectorScale(forward, 1000, forward2);
		VectorScale(forward, 1000, forward3);

		VectorMA(forward2, horizontal,	right,	forward2);
		VectorMA(forward2, vertical,	up,		forward2);

		VectorMA(forward3,  -1*horizontal,	right,	forward3);
		VectorMA(forward3, vertical,		up,		forward3);

		bfg2 = fire_bfg (ent, muzzle, forward2);
		// bfg2->damage		= bfg1->damage;
		// bfg2->splashDamage	= bfg1->splashDamage;

		bfg3 = fire_bfg (ent, muzzle, forward3);
		// bfg3->damage		= bfg1->damage;
		// bfg3->splashDamage	= bfg1->splashDamage;
	}

//	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
}


/*
======================================================================

SHOTGUN

======================================================================
*/

// DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT	are in bg_public.h, because
// client predicts same spreads
#define	DEFAULT_SHOTGUN_DAMAGE	10

qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) {
	trace_t		tr;
	int			damage;
	gentity_t		*traceEnt;

	trap_Trace (&tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT);
	traceEnt = &g_entities[ tr.entityNum ];

	// send bullet impact
	if (  tr.surfaceFlags & SURF_NOIMPACT ) {
		return qfalse;
	}

	if ( traceEnt->takedamage) {
// BEGIN bots - spin
//		damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor;
		damage = BOTS_modifyDamage(ent,BOTS_SHOTGUN_DAMAGE);
// END bots - spin

		G_Damage( traceEnt, ent, ent, forward, tr.endpos,
			damage, 0, MOD_SHOTGUN);
		if( LogAccuracyHit( traceEnt, ent ) ) {
			return qtrue;
		}
	}
	return qfalse;
}

// this should match CG_ShotgunPattern
void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
	int			i;
	float		r, u;
	vec3_t		end;
	vec3_t		forward, right, up;
	int			oldScore;
	qboolean	hitClient = qfalse;

	// derive the right and up vectors from the forward vector, because
	// the client won't have any other information
	VectorNormalize2( origin2, forward );
	PerpendicularVector( right, forward );
	CrossProduct( forward, right, up );

	oldScore = ent->client->ps.persistant[PERS_SCORE];

	// generate the "random" spread pattern
	for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) {
		r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD;
		u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD;
		VectorMA( origin, 8192, forward, end);
		VectorMA (end, r, right, end);
		VectorMA (end, u, up, end);
		if( ShotgunPellet( origin, end, ent ) && !hitClient ) {
			hitClient = qtrue;
			ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
		}
	}
}


void weapon_supershotgun_fire (gentity_t *ent) {
	gentity_t		*tent;

	// send shotgun blast
	tent = G_TempEntity( muzzle, EV_SHOTGUN );
	VectorScale( forward, 4096, tent->s.origin2 );
	SnapVector( tent->s.origin2 );
	tent->s.eventParm = rand() & 255;		// seed for spread pattern
	tent->s.otherEntityNum = ent->s.number;

	ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
}


/*
======================================================================

GRENADE LAUNCHER

======================================================================
*/
void G_ExplodeMissile( gentity_t *ent );

void weapon_grenadelauncher_fire (gentity_t *ent) {
	gentity_t	*m;

	
	if (!ent->client)
		return;

	// extra vertical velocity
	forward[2] += 0.2;
	VectorNormalize( forward );

	m = fire_grenade (ent, muzzle, forward);
// BEGIN BOTS - nits
	if (ent->client->qb_grenMode)
		m->damage = BOTS_HAND_GRENADE_DAMAGE;

	m->damage		= BOTS_modifyDamage(ent,m->damage);
	m->splashDamage	= BOTS_modifyDamage(ent,m->splashDamage);

	// used for faking out cgame so we can use other models
	m->s.modelindex2 = 0;

	// hand grenades
	if (ent->client->qb_grenMode)
	{
		if (ent->client->qb_proxMode)
		{
			if (ent->client->i_numProx < 8)
			{
				int modifier = 1;

				m->classname = "proxy_grenade";
				m->think = BOTS_proxy_think;
				m->nextthink = level.time + FRAMETIME;
				m->s.loopSound = G_SoundIndex("sound/Hgrenc1b.wav");
				m->s.modelindex2 = 5;

				if (ent->client->pers.classlevel > 0)
					modifier = ent->client->pers.classlevel;
				m->i_proxTime = level.time + 30000 * modifier;

				ent->client->i_numProx++;
			}
			else
				m->classname = "hand_grenade";
		}// proxies

		// flash grenades
		else if (ent->client->qb_flashMode)
		{
			m->classname = "flash_grenade";
			m->splashDamage = qfalse;
			m->damage = 0;
			m->think = BOTS_blind_explode;
			m->s.modelindex2 = 6;
		}
		
		// teleport grenades
		else if (ent->client->qb_teleportMode		&& 
				 ent->client->pers.classlevel >= 1	&&
				 ent->bots_class == BOTS_SCIENTIST)
		{
			m->classname	= "teleport_grenade";
			m->think		= BOTS_teleport_explode;
			m->splashDamage = qfalse;
			m->damage		= 0;
			m->s.modelindex2 = 7;
		}

		// decoy grenades
		else if (ent->client->qb_decoyGrenMode)
		{
			if (ent->client->i_numDecoys < 4 + ent->client->pers.classlevel)
			{
				m->classname	 = "decoy_grenade";
				m->s.modelindex2 = (level.time % 1000) / 100;
				m->s.modelindex2 += 30;
				m->think		 = BOTS_proxy_think;
				m->nextthink	 = level.time + FRAMETIME;
				m->i_proxTime	 = level.time + 25000;

				ent->client->i_numDecoys++;
			}
			else
				m->classname = "hand_grenade";
		}

		// NUKE
		else if (ent->client->qb_nukeMode)
		{
			m->classname	= "nuke";
			m->methodOfDeath = MOD_NUKE;
			m->splashMethodOfDeath = MOD_NUKE;
			m->damage		= ent->client->ps.ammo[WP_GRENADE_LAUNCHER] * 25;
			m->splashDamage = ent->client->ps.ammo[WP_GRENADE_LAUNCHER] * 25;
			m->splashRadius = ent->client->ps.ammo[WP_GRENADE_LAUNCHER] * 100;

			m->s.loopSound = G_SoundIndex("sound/siren.wav");
			//m->soundPos1 = G_SoundIndex("sound/siren.wav");
			//G_AddEvent(m, EV_GENERAL_SOUND, m->soundPos1);
			m->s.modelindex2 = 3;
			m->i_proxTime = level.time + 10000;
			m->nextthink = level.time + 10000;

			ent->client->ps.ammo[WP_GRENADE_LAUNCHER] = 0;
			ent->client->qb_nukeMode = qfalse;
		}

		return;
	}

	// detmode
	if (ent->client->qb_detMode)
	{
		// standard deploy mode
		if (!ent->client->qb_settrap1 && !ent->client->qb_settrap2)
		{
			if (ent->client->i_dettrap1 < 4)
			{
				m->classname	= "dettrap1";
				m->nextthink	= 0;
				ent->client->i_dettrap1++;
			}
			else if (ent->client->i_dettrap2 < ent->client->pers.classlevel)
			{
				m->classname	= "dettrap2";
				m->nextthink	= 0;
				ent->client->i_dettrap2++;
			}
		}// end standard deploy mode

		// specified deploy mode
		else
		{
			if (ent->client->i_dettrap1 + ent->client->i_dettrap2 < 4 + ent->client->pers.classlevel)
			{
				if (ent->client->qb_settrap1)
				{
					m->classname	= "dettrap1";
					m->nextthink	= 0;
					ent->client->i_dettrap1++;
				}
				else if (ent->client->qb_settrap2)
				{
					m->classname	= "dettrap2";
					m->nextthink	= 0;
					ent->client->i_dettrap2++;
				}
			}
		}// end specified deploy mode
	
		// use a new model for the detpipes
		if (Q_stricmp (m->classname, "dettrap1") == 0 ||
			Q_stricmp (m->classname, "dettrap2") == 0)
		{
			m->s.modelindex2 = 4;
			m->damage *= .75;
			return;
		}

	}// end detmode

	// cluster mode
	if (ent->client->qb_clusterMode		&& 
			 ent->client->pers.classlevel >= 2	&&
			 ent->bots_class == BOTS_KAMIKAZEE)
	{
		m->classname	= "cluster_grenade";
		m->think		= BOTS_cluster_explode;
	}

// END bots - nits

//	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
}

/*
======================================================================

ROCKET

======================================================================
*/

void Weapon_RocketLauncher_Fire (gentity_t *ent) {
// BEGIN bots - nits
	gentity_t*	r1;
	gentity_t*	r2;
	gentity_t*	r3;

	r1 = fire_rocket (ent, muzzle, forward);
	r1->damage			= BOTS_modifyDamage(ent,r1->damage);
	r1->splashDamage	= BOTS_modifyDamage(ent,r1->splashDamage);

	// these are tag shots... return
	if (ent->tagEnt)
	{
		r1->think = BOTS_tag_think;
		r1->nextthink = level.time + FRAMETIME;

		return;
	}

	if (ent->bots_class == BOTS_SOLDIER && ent->client->weapontoggle)
	{
		vec3_t	forward2, forward3;
		int		horizontal;
		int		vertical;

		if (ent->client->i_splitmode == 1)
		{
			horizontal	= 40;
			vertical	= -40;
		}
		else if (ent->client->i_splitmode == 2)
		{
			horizontal	= 70;
			vertical	= -20;
		}
		else
		{
			horizontal	= 95;
			vertical	= -20;
		}

		VectorScale(forward, 1000, forward2);
		VectorScale(forward, 1000, forward3);

		VectorMA(forward2, horizontal,	right,	forward2);
		VectorMA(forward2, vertical,	up,		forward2);

		VectorMA(forward3,  -1*horizontal,	right,	forward3);
		VectorMA(forward3, vertical,		up,		forward3);

		r2 = fire_rocket (ent, muzzle, forward2);
		// r2->damage			= r1->damage;
		// r2->splashDamage	= r1->splashDamage;

		r3 = fire_rocket (ent, muzzle, forward3);
		// r3->damage			= r1->damage;
		// r3->splashDamage	= r1->splashDamage;
	}
// END bots - nits

//	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
}


/*
======================================================================

PLASMA GUN

======================================================================
*/

void Weapon_Plasmagun_Fire (gentity_t *ent) {
	gentity_t	*m;

	m = fire_plasma (ent, muzzle, forward);
	m->damage		= BOTS_modifyDamage(ent,m->damage);
	m->splashDamage	= BOTS_modifyDamage(ent,m->splashDamage);
//	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );	// "real" physics
}

/*
======================================================================

RAILGUN

======================================================================
*/


/*
=================
weapon_railgun_fire
=================
*/
#define	MAX_RAIL_HITS	4
void weapon_railgun_fire (gentity_t *ent) {
	vec3_t		end;
	trace_t		trace;
	gentity_t	*tent;
	gentity_t	*traceEnt;
	int			damage;
	int			radiusDamage;
	int			i;
	int			hits;
	int			unlinked;
	gentity_t	*unlinkedEntities[MAX_RAIL_HITS];
	int			bots_mask = MASK_SHOT;

// BEGIN bots - spin	
	damage = BOTS_modifyDamage(ent,BOTS_RAILGUN_DAMAGE);
// END bots - spin	
	
	radiusDamage = 30 * s_quadFactor;

// BEGIN bots - nits
	if (ent->bots_class == BOTS_SNIPER)
	{
		if (ent->client->invisible)
		{
			damage *= 3;
			
			// cloaked and sighted does serious damage
			if (ent->lasersight && level.time > ent->client->i_sightTime)
			{
				damage *= 3;
			}
		}
		else if (ent->lasersight && level.time > ent->client->i_sightTime)
			damage += 75;

		if (ent->client->pers.classlevel > 2)
			bots_mask = (CONTENTS_BODY|CONTENTS_CORPSE);
	}
// END bots - nits

	VectorMA (muzzle, 8192, forward, end);

	// trace only against the solids, so the railgun will go through people
	unlinked = 0;
	hits = 0;
	do {
// BEGIN bots - nits
		trap_Trace (&trace, muzzle, NULL, NULL, end, ent->s.number, bots_mask );
// END bots - nits
		if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
			break;
		}
		traceEnt = &g_entities[ trace.entityNum ];
		if ( traceEnt->takedamage ) {
			if( LogAccuracyHit( traceEnt, ent ) ) {
				hits++;
			}
			G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
		}
		if ( trace.contents & CONTENTS_SOLID ) {
			break;		// we hit something solid enough to stop the beam
		}
		// unlink this entity, so the next trace will go past it
		trap_UnlinkEntity( traceEnt );
		unlinkedEntities[unlinked] = traceEnt;
		unlinked++;
	} while ( unlinked < MAX_RAIL_HITS );

	// link back in any entities we unlinked
	for ( i = 0 ; i < unlinked ; i++ ) {
		trap_LinkEntity( unlinkedEntities[i] );
	}

// BEGIN bots - nits
	if (ent->client->pers.classlevel >= 3)
		return;
// END bots - nits

	// the final trace endpos will be the terminal point of the rail trail

	// snap the endpos to integers to save net bandwidth, but nudged towards the line
	SnapVectorTowards( trace.endpos, muzzle );

	// send railgun beam effect
	tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );

	// set player number for custom colors on the railtrail
	tent->s.clientNum = ent->s.clientNum;

	VectorCopy( muzzle, tent->s.origin2 );

	// move origin a bit to come closer to the drawn gun muzzle
	VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
	VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );

	// no explosion at end if SURF_NOIMPACT, but still make the trail
	if ( trace.surfaceFlags & SURF_NOIMPACT ) {
		tent->s.eventParm = 255;	// don't make the explosion at the end
	} else {
		tent->s.eventParm = DirToByte( trace.plane.normal );
	}
	tent->s.clientNum = ent->s.clientNum;

// BEGIN bots - nits
	/*
	// give the shooter a reward sound if they have made two railgun hits in a row
	if ( hits == 0 ) {
		// complete miss
		ent->client->accurateCount = 0;
	} else {
		// check for "impressive" reward sound
		ent->client->accurateCount += hits;
		if ( ent->client->accurateCount >= 2 ) {
			ent->client->accurateCount -= 2;
			ent->client->ps.persistant[PERS_REWARD_COUNT]++;
			ent->client->ps.persistant[PERS_REWARD] = REWARD_IMPRESSIVE;
			ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
			// add the sprite over the player's head
			ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET );
			ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
			ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
		}
		ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
	}
	*/
// END bots - nits
}


/*
======================================================================

GRAPPLING HOOK

======================================================================
*/

void Weapon_GrapplingHook_Fire (gentity_t *ent)
{
// BEGIN bots - nits
	// make visibile if we are a cloaked sniper
	if (ent->bots_class == BOTS_SNIPER)
	{
		if (ent->client->invisible)
			BOTS_sniper_cloak(ent->s.clientNum, qfalse);
		ent->client->last_movement = level.time;	
	}
// END bots -nits

	if (!ent->client->hook) {
		gentity_t	*m = fire_grapple (ent, muzzle, forward);
		m->damage = BOTS_modifyDamage(ent,m->damage);
	}

//	if (!ent->client->fireHeld && !ent->client->hook)
//		fire_grapple (ent, muzzle, forward);

	ent->client->fireHeld = qtrue;
}

void Weapon_HookFree (gentity_t *ent)
{
// BEGIN bots - tbp
	if (ent->parent->client->trooptransport)
		BOTS_BerzerkerReleaseOne(ent->parent);
// END bots - tbp

	ent->parent->client->hook = NULL;
	ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL;
	G_FreeEntity( ent );
}

void Weapon_HookThink (gentity_t *ent)
{
	if (ent->enemy) {
		vec3_t v, oldorigin;

// BEGIN bots - tbp
		// Perhaps should check a max distance, and if hit => release 
		if (ent->parent->client->trooptransport) {
			// We're on a zerk bus... Try to be smart. doh!
			gentity_t	*zerk = ent->enemy;
			float		yaw;
			const float 
				fDISTANCE		= 35.0f;
				// fDISTANCE_MAX	= 500.0f; // Too large?


			VectorCopy(ent->r.currentOrigin, oldorigin);

			// Compute where that damn zerk is
			v[0] = zerk->r.currentOrigin[0] + (zerk->r.mins[0] + zerk->r.maxs[0]) * 0.5;
			v[1] = zerk->r.currentOrigin[1] + (zerk->r.mins[1] + zerk->r.maxs[1]) * 0.5;
			v[2] = zerk->r.currentOrigin[2] + (zerk->r.mins[2] + zerk->r.maxs[2]) * 1.0;
			// Aim for his back
			yaw = BOTS_BerzerkerComputeAngleFromRank(zerk, ent->parent->client->iTroopRank);
			v[0] += cos(yaw) * fDISTANCE;
			v[1] += sin(yaw) * fDISTANCE;


			SnapVectorTowards( v, oldorigin );	// save net bandwidth
			G_SetOrigin( ent, v );			
			
			ent->nextthink = level.time + 10; // FRAMETIME
		}
// END bots - tbp
		else {
			VectorCopy(ent->r.currentOrigin, oldorigin);
			v[0] = ent->enemy->r.currentOrigin[0] + (ent->enemy->r.mins[0] + ent->enemy->r.maxs[0]) * 0.5;
			v[1] = ent->enemy->r.currentOrigin[1] + (ent->enemy->r.mins[1] + ent->enemy->r.maxs[1]) * 0.5;
			v[2] = ent->enemy->r.currentOrigin[2] + (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5;
			SnapVectorTowards( v, oldorigin );	// save net bandwidth
			
			G_SetOrigin( ent, v );
		}
	}

	VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
}

/*
======================================================================

LIGHTNING GUN

======================================================================
*/

void Weapon_LightningFire( gentity_t *ent ) {
	trace_t		tr;
	vec3_t		end;
	gentity_t	*traceEnt, *tent;
	int			damage;

// BEGIN bots - nits
	// beef up the LG a bit
	damage = BOTS_modifyDamage(ent,BOTS_LIGHTNING_DAMAGE);
// END bots - nits

	VectorMA( muzzle, LIGHTNING_RANGE, forward, end );

	trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );

	if ( tr.entityNum == ENTITYNUM_NONE ) {
		return;
	}

	traceEnt = &g_entities[ tr.entityNum ];

	if ( traceEnt->takedamage && traceEnt->client ) {
		tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
		tent->s.otherEntityNum = traceEnt->s.number;
		tent->s.eventParm = DirToByte( tr.plane.normal );
		tent->s.weapon = ent->s.weapon;
		if( LogAccuracyHit( traceEnt, ent ) ) {
			ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
		}
	} else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
		tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
		tent->s.eventParm = DirToByte( tr.plane.normal );
	}

	if ( traceEnt->takedamage) {
		G_Damage( traceEnt, ent, ent, forward, tr.endpos,
			damage, 0, MOD_LIGHTNING);
	}
}

//======================================================================


/*
===============
LogAccuracyHit
===============
*/
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
	if( !target->takedamage ) {
		return qfalse;
	}

	if ( target == attacker ) {
		return qfalse;
	}

	if( !target->client ) {
		return qfalse;
	}

	if( !attacker->client ) {
		return qfalse;
	}

	if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
		return qfalse;
	}

	if ( OnSameTeam( target, attacker ) ) {
		return qfalse;
	}

	return qtrue;
}


/*
===============
CalcMuzzlePoint

set muzzle location relative to pivoting eye
===============
*/
void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
	VectorCopy( ent->s.pos.trBase, muzzlePoint );
	muzzlePoint[2] += ent->client->ps.viewheight;
	VectorMA( muzzlePoint, 14, forward, muzzlePoint );
	// snap to integer coordinates for more efficient network bandwidth usage
	SnapVector( muzzlePoint );
}


// BEGIN bots - spin
#define	MAX_STINGER_HITS	4
void BOTS_weapon_stinger_fire (gentity_t* ent) {
	vec3_t		end;
	trace_t		trace;
	gentity_t	*tent;
	gentity_t	*traceEnt;
	int			damage;
	int			radiusDamage;
	int			i;
	int			hits;
	int			unlinked;
	gentity_t	*unlinkedEntities[MAX_STINGER_HITS];
	int			bots_mask = MASK_SHOT;

	damage = BOTS_modifyDamage(ent,BOTS_STINGER_DAMAGE);
	radiusDamage = 0 * s_quadFactor;

	VectorMA (muzzle, 8192, forward, end);

	// trace only against the solids, so the railgun will go through people
	unlinked = 0;
	hits = 0;
	do {
// BEGIN bots - nits
		trap_Trace (&trace, muzzle, NULL, NULL, end, ent->s.number, bots_mask );
// END bots - nits
		if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
			break;
		}
		traceEnt = &g_entities[ trace.entityNum ];
		if ( traceEnt->takedamage ) {
			if( LogAccuracyHit( traceEnt, ent ) ) {
				hits++;
			}
			G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_STINGER);
		}
		if ( trace.contents & CONTENTS_SOLID ) {
			break;		// we hit something solid enough to stop the beam
		}
		// unlink this entity, so the next trace will go past it
		trap_UnlinkEntity( traceEnt );
		unlinkedEntities[unlinked] = traceEnt;
		unlinked++;
	} while ( unlinked < MAX_STINGER_HITS );

	// link back in any entities we unlinked
	for ( i = 0 ; i < unlinked ; i++ ) {
		trap_LinkEntity( unlinkedEntities[i] );
	}

	// the final trace endpos will be the terminal point of the rail trail

	// snap the endpos to integers to save net bandwidth, but nudged towards the line
	SnapVectorTowards( trace.endpos, muzzle );

	// send railgun beam effect
	tent = G_TempEntity( trace.endpos, EV_STINGER );

	// set player number for custom colors on the railtrail
	tent->s.clientNum = ent->s.clientNum;

	VectorCopy( muzzle, tent->s.origin2 );

	// move origin a bit to come closer to the drawn gun muzzle
	VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
	VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );

	// no explosion at end if SURF_NOIMPACT, but still make the trail
	if ( trace.surfaceFlags & SURF_NOIMPACT ) {
		tent->s.eventParm = 255;	// don't make the explosion at the end
	} else {
		tent->s.eventParm = DirToByte( trace.plane.normal );
	}
	tent->s.clientNum = ent->s.clientNum;
}
// END bots - spin

/*
===============
FireWeapon
===============
*/
void FireWeapon( gentity_t *ent ) {
	int li_modifier;

	BOTS_computeQuadFactor(ent);

	// track shots taken for accuracy tracking.  Grapple is not a weapon and gauntet is just not tracked
	if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
		ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
	}

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, forward, right, up);

	CalcMuzzlePoint ( ent, forward, right, up, muzzle );

	// fire the specific weapon
	switch( ent->s.weapon ) {
// BEGIN bots - spin
	case WP_STINGER:
		{
			BOTS_weapon_stinger_fire( ent );
			
			// poison sucks cells and puts the client in shrooms mode
			if (ent->client->qb_poisonMode && ent->bots_class == BOTS_NURSE)
			{
				if (ent->client->ps.ammo[WP_BFG] < 1)
					BOTS_poison(ent->s.clientNum);
				else
					ent->client->ps.ammo[WP_BFG] -= 1;
			}
		}
		break;
//	case WP_KNIFE:
//		break;
// END bots - spin
	case WP_GAUNTLET:
		Weapon_Gauntlet( ent );
		break;
	case WP_LIGHTNING:
		Weapon_LightningFire( ent );
		break;
	case WP_SHOTGUN:
		weapon_supershotgun_fire( ent );
		break;
	case WP_MACHINEGUN:
// BEGIN bots - nits
			Bullet_Fire( ent, MACHINEGUN_SPREAD, BOTS_modifyDamage(ent,BOTS_MACHINEGUN_DAMAGE) );
// END bots - nits
		break;
	case WP_GRENADE_LAUNCHER:
		if (ent->bots_class == BOTS_KAMIKAZEE && ent->client->qb_clusterMode)
		{
			if (!ent->client->qb_detMode ||
				ent->client->i_dettrap1 + ent->client->i_dettrap2 >= 4 + ent->client->pers.classlevel)
			{
				if (ent->client->ps.ammo[WP_GRENADE_LAUNCHER] >= 5)
					ent->client->ps.ammo[WP_GRENADE_LAUNCHER] -= 4;
				else
					BOTS_normal(ent->s.clientNum);
			}
		}

		weapon_grenadelauncher_fire( ent );
		break;
// BEGIN bots - nits
	case WP_ROCKET_LAUNCHER:
		if (ent->client->qb_tagMode)
		{
			if (!ent->tagEnt)
			{
				BOTS_fire_tag(ent, muzzle, forward);
				//ent->client->ps.weaponTime += 500;
			}
			else
				Weapon_RocketLauncher_Fire(ent);
		}

		else
		{
			if (ent->client->pers.classlevel >= 2 && ent->client->weapontoggle)
			{
				if (ent->client->ps.ammo[WP_ROCKET_LAUNCHER] >= 3)
					ent->client->ps.ammo[WP_ROCKET_LAUNCHER] -= 2;
				else
					BOTS_weaponToggle(ent->client->ps.clientNum, BOTS_SOLDIER);
			}
			Weapon_RocketLauncher_Fire(ent);
		}
		break;
	case WP_PLASMAGUN:
		if (ent->bots_class == BOTS_INFILTRATOR &&
			ent->client->pers.classlevel < 4	&&
			ent->client->qb_disguiseMode)
		{
			ent->client->i_disguiseStopTime = level.time;
			ent->client->qb_disguiseMode = qfalse;
			ClientUserinfoChanged( ent->s.clientNum );
			
			// force the loading of the players model to all clients
			//csfix: trap_SendServerCommand( -1, "loaddefered\n" );
		}
		Weapon_Plasmagun_Fire( ent );
		break;
	case WP_RAILGUN:
		weapon_railgun_fire( ent );
		break;
	case WP_BFG:
		if (ent->client->pers.classlevel == 4 && ent->client->weapontoggle)
		{
			if (ent->client->ps.ammo[WP_BFG] >= 3)
				ent->client->ps.ammo[WP_BFG] -= 2;
			else
				BOTS_weaponToggle(ent->client->ps.clientNum, BOTS_CAPTAIN);
		}
		BFG_Fire( ent );
		break;
	case WP_GRAPPLING_HOOK:
		Weapon_GrapplingHook_Fire( ent );
		break;
	default:
// FIXME		G_Error( "Bad ent->s.weapon" );
		break;
	}

// BEGIN bots - spin
	// The base times were taken from PM_Weapon in bg_pmove.c If any of the times are
	// changed there they need to be synched with the base times here.

	if (ent->bots_team == TEAM_RED)
		li_modifier = 1;
	else
		li_modifier = 0;
	
	
	switch( ent->s.weapon ) {
	case WP_GAUNTLET:
		ent->client->ps.weaponTime = 400 - (int)(200 * ent->fCachedFireRate / 4) - (20 * li_modifier);
		break;
	case WP_LIGHTNING:
		ent->client->ps.weaponTime = 50 - (int)(25 * ent->fCachedFireRate  / 4) - (3 * li_modifier);
		break;
	case WP_SHOTGUN:
		ent->client->ps.weaponTime = 1000 - (int)(500 * ent->fCachedFireRate  / 4) - (50 * li_modifier);
		break;
	case WP_MACHINEGUN:
		ent->client->ps.weaponTime = 110 - (int)(55 * ent->fCachedFireRate  / 4) - (6 * li_modifier);
		break;
	case WP_GRENADE_LAUNCHER:
		{
			int weaponTime = 800;
			// kami clusters fire at a slower rate
			if (ent->bots_class == BOTS_KAMIKAZEE && ent->client->qb_clusterMode)
			{
				// seems redundant...
				weaponTime = 1200;
				if (ent->client->qb_detMode &&
					ent->client->i_dettrap1 + ent->client->i_dettrap2 < 4 + ent->client->pers.classlevel)
						weaponTime = 800;
			}

			ent->client->ps.weaponTime = weaponTime - (int)(400 * ent->fCachedFireRate / 4) - (40 * li_modifier);
		}
		break;
	case WP_ROCKET_LAUNCHER:
		ent->client->ps.weaponTime = 800 - (int)(400 * ent->fCachedFireRate / 4) - (40 * li_modifier);
		break;
	case WP_PLASMAGUN:
		ent->client->ps.weaponTime = 100 - (int)(50 * ent->fCachedFireRate / 4) - (5 * li_modifier);
		break;
	case WP_RAILGUN:
		ent->client->ps.weaponTime = 1500 - (int)(750 * ent->fCachedFireRate / 4) - (75 * li_modifier);
		break;
	case WP_BFG:
		ent->client->ps.weaponTime = 600 - (int)(300 * ent->fCachedFireRate / 4) - (30 * li_modifier);
		break;
	case WP_STINGER:
		ent->client->ps.weaponTime = 200 - (int)(100 * ent->fCachedFireRate / 4) - (10 * li_modifier);
		break;
	}
// END bots - spin
}

